﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using NLog;

namespace DotNetDream.Data.Helper.EntityFramework
{
    public abstract class RepositoryBase<TContext> : IDisposable
        where TContext : DbContext, new()
    {
        #region Class fields
        /// <summary>
        /// Database instance
        /// </summary>
        protected TContext Database
        {
            get
            {
                return _database;
            }
        }
        private TContext _database;
        /// <summary>
        /// Data access logger
        /// </summary>
        protected static Logger DataLogger = LogManager.GetCurrentClassLogger();
        #endregion

        #region Properties
        #endregion

        /// <summary>
        /// Default constructor
        /// </summary>
        public RepositoryBase()
        {
            _database = new TContext();
        }

        /// <summary>
        /// Dipose repository
        /// </summary>
        public void Dispose()
        {
            if (_database != null)
            {
                _database.Dispose();
                _database = null;
            }
        }

        protected void LogException(Exception ex, bool fatal)
        {
            #region Get calling method name and class
            MethodBase method = new StackFrame(1).GetMethod();
            string methodName = (method == null) ? "UNKNOWN" : method.Name;

            string name = string.Concat(GetType().ToString(), "->", method.Name, "(");
            foreach (ParameterInfo param in method.GetParameters())
            {
                name += param.Name + ", ";
            }
            name = name.TrimEnd(new char[] { ',', ' ' }) + ")";
            #endregion

            //Log the exception
            if (fatal) DataLogger.FatalException(name, ex);
            else DataLogger.ErrorException(name, ex);
        }

        #region Generic CRUD operations

        #region Select
        /// <summary>
        /// Select data from database
        /// </summary>
        /// <typeparam name="TItem">Type of data to select</typeparam>
        /// <returns></returns>
        public IQueryable<TEntity> Select<TEntity>(Expression<Func<TEntity, bool>> condition)
           where TEntity : class
        {
            //TODO: Remove test logging
            DataLogger.Debug("This is a logging test!");
            return _database.Set<TEntity>().Where(condition);
        }
        #endregion

        #region Insert
        /// <summary>
        /// Insert new item into database
        /// </summary>
        /// <typeparam name="TItem">Type of item to insert</typeparam>
        /// <param name="item">Item to insert</param>
        /// <returns>Inserted item</returns>
        public OperationStatus Insert<TEntity>(TEntity item)
            where TEntity : class
        {
            var status = new OperationStatus { Action = OperationStatus.Operation.Insert };
            try
            {
                _database.Set<TEntity>().Add(item);
                status.AffectedRows = _database.SaveChanges();
                status.RecordId = _database.Entry(item).Property("Id").CurrentValue;
            }
            catch (Exception ex)
            {
                LogException(ex, false);
                status.FromException(ex);
            }
            return status;
        }

        /// <summary>
        /// Insert range
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public OperationStatus Insert<TEntity>(List<TEntity> items)
            where TEntity : class
        {
            var status = new OperationStatus { Action = OperationStatus.Operation.Insert };
            try
            {
                DbSet<TEntity> table = _database.Set<TEntity>();
                foreach (TEntity item in items)
                {
                    table.Add(item);
                }
                status.AffectedRows = _database.SaveChanges();
            }
            catch (Exception ex)
            {
                LogException(ex, false);
                status.FromException(ex);
            }

            return status;
        }
        #endregion

        #region Update
        /// <summary>
        /// Update an item
        /// </summary>
        /// <typeparam name="TItem">Type of item to update</typeparam>
        /// <param name="item">Item to update</param>
        /// <returns>Updated item</returns>
        public OperationStatus Update<TEntity>(TEntity item)
            where TEntity : class
        {
            var status = new OperationStatus { Action = OperationStatus.Operation.Update };
            try
            {
                _database.Set<TEntity>().Attach(item);
                _database.Entry(item).State = System.Data.EntityState.Modified;
                status.AffectedRows = _database.SaveChanges();
                status.RecordId = _database.Entry(item).Property("Id").CurrentValue;
            }
            catch (Exception ex)
            {
                LogException(ex, false);
                status.FromException(ex);
            }

            return status;
        }

        /// <summary>
        /// Update range
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public OperationStatus Update<TEntity>(List<TEntity> items)
            where TEntity : class
        {
            var status = new OperationStatus { Action = OperationStatus.Operation.Update };
            try
            {
                foreach (TEntity item in items)
                {
                    var entry = _database.Entry(item);
                    entry.State = System.Data.EntityState.Modified;
                }
                status.AffectedRows = _database.SaveChanges();
            }
            catch (Exception ex)
            {
                LogException(ex, false);
                status.FromException(ex);
            }

            return status;
        }
        #endregion

        #region Delete
        /// <summary>
        /// Delete an item
        /// </summary>
        /// <typeparam name="TItem">Type of item to delete</typeparam>
        /// <param name="item">Item to delete</param>
        public OperationStatus Delete<TEntity>(TEntity item)
           where TEntity : class
        {
            var status = new OperationStatus { Action = OperationStatus.Operation.Delete };
            try
            {
                var entry = _database.Entry(item);
                if (entry != null)
                {
                    entry.State = System.Data.EntityState.Deleted;
                }
                status.AffectedRows = _database.SaveChanges();
            }
            catch (Exception ex)
            {
                LogException(ex, false);
                status.FromException(ex);
            }

            return status;
        }

        /// <summary>
        /// Delete range
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public OperationStatus Delete<TEntity>(List<TEntity> items)
            where TEntity : class
        {
            var status = new OperationStatus { Action = OperationStatus.Operation.Delete };
            try
            {
                foreach (TEntity item in items)
                {
                    var entry = _database.Entry(item);
                    if (entry != null)
                    {
                        entry.State = System.Data.EntityState.Deleted;
                    }
                }
                status.AffectedRows = _database.SaveChanges();
            }
            catch (Exception ex)
            {
                LogException(ex, false);
                status.FromException(ex);
            }

            return status;
        }
        #endregion
        #endregion
    }
}
